package org.easydarwin.blogdemos;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import android.view.Surface;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;


public class SponVDecode {
//    private String MIME_TYPE = "video/hevc";
    private String MIME_TYPE = "video/avc";
    private String TAG = getClass().getSimpleName();

    private MediaCodec mDecoder;
    long presentationTimeUs = 0;
    int m_framerate = 15;
    private MediaCodec.BufferInfo bufferInfo;

    private int pcmqueuesize = 10;

    public boolean isRuning() {
        return isRuning;
    }

    private boolean isRuning = false;
    private boolean isInStop = false;
    private boolean isExit = false;

    public boolean isInExit() {
        return isInExit;
    }

    private boolean isInExit = false;
    private Thread DecodeThread;

    public boolean isConnecting = false;
    public boolean isVideoOk = false;
    public int videoWidth = 0;
    public int videoHeight = 0;

    public int createCnt = 0;

    private LinkedBlockingQueue<byte[]> H264Queue = new LinkedBlockingQueue<byte[]>();

    private Surface mSurface;


    ByteBuffer[] decoderInputBuffers = null;
    ByteBuffer[] decoderOutputBuffers = null;
    private MediaFormat format;

    private int count = 0;

    private DecodeRunnable runnable;


    public void init(Surface surface, int Width, int Height) throws IOException {
        this.mSurface = surface;
        Log.d(TAG, "init: ");
        isObtainWH = false;
    }

    private Lock startOrStopLock = new ReentrantLock();

    private void start() {
        startOrStopLock.lock();
        try {
//            Log.d(TAG, "start()");
            mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
//            Log.d(TAG, "createDecoderByType");
            format = MediaFormat.createVideoFormat(MIME_TYPE, videoWidth, videoHeight);
//            Log.d(TAG, "createVideoFormat");
            try {
                if (mDecoder != null) {
                    if (mSurface == null) {
                        mDecoder.configure(format, null, null, 0);
                    } else {
                        if (mSurface.isValid()) {
                            Log.d(TAG, "start: configure");
                            mDecoder.configure(format, mSurface, null, 0);
                        }
                    }
                    mDecoder.start();
                }

                /**
                 * @howrad 2020-07-28
                 * 全局设置decoderInputBuffers
                 */
                decoderInputBuffers = mDecoder.getInputBuffers();
                decoderOutputBuffers = mDecoder.getOutputBuffers();
                bufferInfo = new MediaCodec.BufferInfo();
            } catch (Exception e) {
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            startOrStopLock.unlock();
        }

        StartDecodeThread();
    }


    boolean isObtainWH = false;

    public void putData(byte[] buffer, int length,int codecName) {
        /**
         * @howard 2020-09-05
         * 添加分辨率获取再configure MediaCodec
         */
        if (isInStop) {
            Log.d(TAG, "putData: isInStop");
            return;
        }
//        Log.d(TAG, " putData"+ StringUtil.printBuffer(buffer, 0, 5));

        if (!isObtainWH) {
            int[] size = H264SPSPaser.getSizeFromSps(buffer);

            if (size != null && size[0] > 0 && size[1] > 0 && size[0] < 4092 && size[1] < 4092) {
                videoWidth = size[0];
                videoHeight = size[1];
                Log.d(TAG, "putData: " + this.videoWidth + "X" + this.videoHeight + ",profile_idc=" + size[2] + ",level_id=" + size[3] + ",fps=" + size[4]);
                if (checkSps(videoWidth, videoHeight) == false) {
//                    return;
                }
                isConnecting = true;
                isObtainWH = true;
                if(codecName == 265){
                    MIME_TYPE = "video/hevc";
                }
                start();
            } else {
                return;
            }
        }/*else {
            int[] size = H264SPSPaser.getSizeFromSps(buffer);

            if (size != null && size[0] > 0 && size[1] > 0) {
                videoWidth = size[0];
                videoHeight = size[1];
                Log.d(TAG, "putData: "+this.videoWidth + "X"+this.videoHeight);
                if(checkSps(videoWidth, videoHeight)==false){
                    return;
                }
            }
        }*/

        if (!isObtainWH) {
            return;
        }


        try {
            if (H264Queue != null) {
                if (checkData(buffer))
                    H264Queue.put(buffer);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private boolean checkSps(int videoWidth, int videoHeight) {
//        if(videoHeight<288|| videoHeight<288)
//            return false;
        return !(videoWidth > 4096 || videoHeight > 4096);
    }

    private boolean checkData(byte[] data) {
        if (data != null && (data.length > 4)) {
            if ((data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1) || (data[0] == 0 && data[1] == 0 && data[2] == 1))
                return true;
            else {
                Log.d(TAG, "checkData: error" + StringUtil.printBuffer(data, 0, 4));
            }
        }
        return false;
    }

    /**
     * 描述功能：停止线程
     */
    public void StopThread() {
        Log.d(TAG, "StopThread: ");
        isConnecting = false;
        isInExit = true;
        if (isRuning == false) {
            clearDecoder(true);
        }
        isRuning = false;
    }

    private long startEncodeTime = System.currentTimeMillis();

    /**
     * 编码线程
     */
    public void StartDecodeThread() {
        runnable = new DecodeRunnable();

        DecodeThread = new Thread(runnable);
        DecodeThread.start();
    }

    private void doDecode() {
        boolean bRet = true;
        isRuning = true;
        while (isRuning && mDecoder != null) {
            /**
             * @howard 20210722
             */
            if (needRestart) {
                try {
                    sleep(10);
                    refreshDecoder();
                    needRestart = false;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (H264Queue.size() > 0) {
                try {
                    byte[] input = H264Queue.take();
                    bRet = playH264(input);
                    input = null;

                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                //这里不能去掉，会导致cpu占用变大
                try {
                    sleep(10);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        clearDecoder(bRet);
    }

    private void clearDecoder(boolean bRet) {
        startOrStopLock.lock();
        try {
            isInStop = true;
            if (mDecoder != null) {
                if (bRet == false) {
                    Log.d(TAG, "doDecode: reset");
                    mDecoder.reset();
                } else {
                    Log.d(TAG, "doDecode: exit");
                    mDecoder.stop();
                    sleep(10);
                    mDecoder.release();
                }
                mDecoder = null;
            }

            H264Queue.clear();
            bufferInfo = null;
            H264Queue = null;
            runnable = null;
            if (DecodeThread != null) {
                DecodeThread = null;
            }
            mSurface.release();
            mSurface = null;
            isExit = true;
            isInExit = false;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            startOrStopLock.unlock();
        }
    }

    public boolean checkIsExited() {
        return isExit;
    }

    private class DecodeRunnable implements Runnable {
        private volatile boolean flag = false;

        @Override
        public void run() {
            doDecode();
        }
    }


    /**
     * @howard 20210722
     */
    boolean needRestart = false;

    /**
     * @howard 2019-11-22
     * 添加返回值，异常时重新补帧
     * 会议发起方能恢复正常，但接收方异常时，有时能恢复，有时只能显示I帧（怀疑发送帧有问题，不能恢复时，发起方很卡）
     */
    private boolean playH264(byte[] data) {
        try {
            if ((data != null) && (data.length > 0) && (mDecoder != null)) {

                long pts = System.currentTimeMillis() - startEncodeTime;
                int inputBufferIndex = -1;

                inputBufferIndex = mDecoder.dequeueInputBuffer(-1);
//                inputBufferIndex= mDecoder.dequeueInputBuffer(10000);


                if (inputBufferIndex >= 0) {
                    inputErrorCount = 0;
                    ByteBuffer inputBuffer = decoderInputBuffers[inputBufferIndex];
                    inputBuffer.clear();
                    inputBuffer.put(data);

//                    mDecoder.queueInputBuffer(inputBufferIndex, 0, data.length, pts, 0);
                    mDecoder.queueInputBuffer(inputBufferIndex, 0, data.length, bufferInfo.presentationTimeUs
                            , bufferInfo.flags);

                    presentationTimeUs += 1;

                    //decode
                    int outputBufferIndex = mDecoder.dequeueOutputBuffer(bufferInfo, 10000);//改成-1，防止异常崩溃

                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        Log.d(TAG, "playH264: INFO_TRY_AGAIN_LATER = ");
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                        decoderOutputBuffers = mDecoder.getOutputBuffers();
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        format = mDecoder.getOutputFormat();
                        videoWidth = format.getInteger(MediaFormat.KEY_WIDTH);
                        videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
                        isConnecting = true;


                    } else if (outputBufferIndex < 0) {
                        Log.d(TAG, "playH264: outputBufferIndex = " + outputBufferIndex);
                    } else { // decoderStatus >= 0

                        Log.d(TAG, "playH264:bufferInfo.flags====" + bufferInfo.flags);

                        if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                            Log.d(TAG, "playH264: BUFFER_FLAG_CODEC_CONFIG");
                            mDecoder.releaseOutputBuffer(outputBufferIndex, false);
                            return true;
                        }

                        boolean doRender = (bufferInfo.size != 0);
                        if (doRender == false) {
                            Log.d(TAG, "playH264: doRender ====" + doRender);
                        } else {
                        }

                        mDecoder.releaseOutputBuffer(outputBufferIndex, doRender);

                        if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            return false;
                        }

                    }
                } else {
                    inputErrorCount++;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();

            Log.d(TAG, TAG + "Exception e==" + e.toString());
            count = 0;
            inputErrorCount++;
            return false;
        } finally {
            if (inputErrorCount > 60) {
                Log.d(TAG, "playH264ssssss: reset");
//                try {
                inputErrorCount = 0;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    mDecoder.reset();
                }
                createCnt++;
                if (createCnt > 1) {
                    Log.d(TAG, "createCnt: " + createCnt);
                }


//                    mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
//
//                    format = MediaFormat.createVideoFormat(MIME_TYPE, videoWidth, videoHeight);
//                    if (mDecoder != null) {
//                        //configure异常后，不能恢复
//                        //2021-07-21 20:11:35.161 26169-27450/spon.rdc.new8531v E/BufferQueueProducer: [SurfaceTexture-0-26169-5] connect: already connected (cur=3 req=3)
//                        //2021-07-21 20:11:35.161 26169-27450/spon.rdc.new8531v E/MediaCodec: native_window_api_connect returned an error: Invalid argument (-22)
//                        //2021-07-21 20:11:35.161 26169-27412/spon.rdc.new8531v E/MediaCodec: configure failed with err 0xffffffea, resetting...
//                        mDecoder.configure(format, mSurface, null, 0);
//                        mDecoder.start();
//
//                        /**
//                         * @howrad 2020-07-28
//                         * 全局设置decoderInputBuffers
//                         */
//                        decoderInputBuffers = mDecoder.getInputBuffers();
//                        decoderOutputBuffers = mDecoder.getOutputBuffers();
//
//                        H264Queue.clear();
//                    }
//                } catch (IOException e1) {
//                    e1.printStackTrace();
//                }
            }
        }
        return true;
    }

    public void clearData() {
        H264Queue.clear();
    }

    private void refreshDecoder() {
//        try {
//            Log.d(TAG, "refreshDecoder: ");
//            mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
//
//            format = MediaFormat.createVideoFormat(MIME_TYPE, videoWidth, videoHeight);
//            if (mDecoder != null) {
//                //configure异常后，不能恢复
//                //2021-07-21 20:11:35.161 26169-27450/spon.rdc.new8531v E/BufferQueueProducer: [SurfaceTexture-0-26169-5] connect: already connected (cur=3 req=3)
//                //2021-07-21 20:11:35.161 26169-27450/spon.rdc.new8531v E/MediaCodec: native_window_api_connect returned an error: Invalid argument (-22)
//                //2021-07-21 20:11:35.161 26169-27412/spon.rdc.new8531v E/MediaCodec: configure failed with err 0xffffffea, resetting...
//                mDecoder.configure(format, mSurface, null, 0);
//                mDecoder.start();
//
//                /**
//                 * @howrad 2020-07-28
//                 * 全局设置decoderInputBuffers
//                 */
//                decoderInputBuffers = mDecoder.getInputBuffers();
//                decoderOutputBuffers = mDecoder.getOutputBuffers();
//
//                H264Queue.clear();
//            }
//        } catch (IOException e1) {
//            e1.printStackTrace();
//        }
    }

    private int inputErrorCount = 0;

}